﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace NMF.Expressions
{
    internal abstract class ObservableMethodBase<T, TDelegate, TResult> : ObservableExpression<TResult>
        where TDelegate : class
    {
        public ObservableMethodBase(IObservableExpression<T> target, MethodInfo method)
        {
            if (method == null) throw new ArgumentNullException("method");
            if (target == null) throw new ArgumentNullException("target");

            Target = target;
            Method = method;

            target.ValueChanged += TargetChanged;

            this.ValueChanged += MyValueChanged;
        }

        private void RenewFunction()
        {
            Function = ReflectionHelper.CreateDelegate(typeof(TDelegate), Target.Value, Method) as TDelegate;
        }

        public MethodInfo Method { get; private set; }

        public TDelegate Function { get; private set; }

        private void TargetChanged(object sender, ValueChangedEventArgs e)
        {
            if (!IsAttached) return;
            var oldNotifier = e.OldValue as INotifyCollectionChanged;
            var newNotifier = e.NewValue as INotifyCollectionChanged;
            if (oldNotifier != null)
            {
                oldNotifier.CollectionChanged -= TargetCollectionChanged;
            }
            if (newNotifier != null)
            {
                newNotifier.CollectionChanged += TargetCollectionChanged;
            }
            RenewFunction();
            Refresh();
        }

        private void TargetCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (!IsAttached) return;
            Refresh();
        }

        private void MyValueChanged(object sender, ValueChangedEventArgs e)
        {
            var disposable = e.OldValue as IDisposable;
            if (disposable != null)
            {
                disposable.Dispose();
            }
        }

        protected void ArgumentChanged(object sender, ValueChangedEventArgs e)
        {
            if (!IsAttached) return;
            Refresh();
        }

        public override ExpressionType NodeType
        {
            get
            {
                return ExpressionType.Invoke;
            }
        }

        public override bool CanBeConstant
        {
            get
            {
                return Target.CanBeConstant && !(Target.Value is INotifyCollectionChanged);
            }
        }

        public IObservableExpression<T> Target { get; private set; }

        protected override void AttachCore()
        {
            Target.Attach();
            var targetNotifier = Target.Value as INotifyCollectionChanged;
            if (targetNotifier != null)
            {
                targetNotifier.CollectionChanged += TargetCollectionChanged;
            }
            RenewFunction();
        }

        protected override void DetachCore()
        {
            Target.Detach();
            var targetNotifier = Target.Value as INotifyCollectionChanged;
            if (targetNotifier != null)
            {
                targetNotifier.CollectionChanged -= TargetCollectionChanged;
            }
        }
    }

    internal class ObservableMethodCall<T, TResult> : ObservableMethodBase<T, Func<TResult>, TResult>
    {
        public ObservableMethodCall(MethodCallExpression node, ObservableExpressionBinder binder)
            : this(binder.VisitObservable<T>(node.Object), node.Method) { }

        public ObservableMethodCall(IObservableExpression<T> target, MethodInfo method)
            : base(target, method) { }

        protected override TResult GetValue()
        {
            return Function();
        }

        public override bool IsParameterFree
        {
            get { return Target.IsParameterFree; }
        }

        public override IObservableExpression<TResult> ApplyParameters(IDictionary<string, object> parameters)
        {
            return new ObservableMethodCall<T, TResult>(Target.ApplyParameters(parameters), Method);
        }
    }

    internal class ObservableMethodCall<T, T1, TResult> : ObservableMethodBase<T, Func<T1, TResult>, TResult>
    {
        public ObservableMethodCall(MethodCallExpression node, ObservableExpressionBinder binder)
            : this(binder.VisitObservable<T>(node.Object), node.Method, binder.VisitObservable<T1>(node.Arguments[0])) { }

        public ObservableMethodCall(IObservableExpression<T> target, MethodInfo method, IObservableExpression<T1> arg1)
            : base(target, method)
        {
            if (arg1 == null) throw new ArgumentNullException("arg1");

            Argument1 = arg1;
        }

        public IObservableExpression<T1> Argument1 { get; private set; }

        protected override TResult GetValue()
        {
            return Function(Argument1.Value);
        }

        protected override void AttachCore()
        {
            base.AttachCore();
            Argument1.Attach();
            Argument1.ValueChanged += ArgumentChanged;
        }

        protected override void DetachCore()
        {
            base.DetachCore();
            Argument1.ValueChanged -= ArgumentChanged;
            Argument1.Detach();
        }

        public override bool CanBeConstant
        {
            get
            {
                return base.CanBeConstant && Argument1.CanBeConstant;
            }
        }

        public override bool IsParameterFree
        {
            get { return Target.IsParameterFree && Argument1.IsParameterFree; }
        }

        public override IObservableExpression<TResult> ApplyParameters(IDictionary<string, object> parameters)
        {
            return new ObservableMethodCall<T, T1, TResult>(Target.ApplyParameters(parameters), Method, Argument1.ApplyParameters(parameters));
        }
    }

    internal class ObservableMethodCall<T, T1, T2, TResult> : ObservableMethodBase<T, Func<T1, T2, TResult>, TResult>
    {
        public ObservableMethodCall(MethodCallExpression node, ObservableExpressionBinder binder)
            : this(binder.VisitObservable<T>(node.Object), node.Method,
                binder.VisitObservable<T1>(node.Arguments[0]),
                binder.VisitObservable<T2>(node.Arguments[1])) { }

        public ObservableMethodCall(IObservableExpression<T> target, MethodInfo method, IObservableExpression<T1> arg1, IObservableExpression<T2> arg2)
            : base(target, method)
        {
            if (arg1 == null) throw new ArgumentNullException("arg1");
            if (arg2 == null) throw new ArgumentNullException("arg2");

            Argument1 = arg1;
            Argument2 = arg2;
        }

        public IObservableExpression<T1> Argument1 { get; private set; }

        public IObservableExpression<T2> Argument2 { get; private set; }

        protected override TResult GetValue()
        {
            return Function(Argument1.Value, Argument2.Value);
        }

        protected override void AttachCore()
        {
            base.AttachCore();
            var argChanged = new EventHandler<ValueChangedEventArgs>(ArgumentChanged);
            Argument1.Attach();
            Argument1.ValueChanged += argChanged;
            Argument2.Attach();
            Argument2.ValueChanged += argChanged;
        }

        protected override void DetachCore()
        {
            base.DetachCore();
            var argChanged = new EventHandler<ValueChangedEventArgs>(ArgumentChanged);
            Argument1.ValueChanged -= argChanged;
            Argument1.Detach();
            Argument2.ValueChanged -= argChanged;
            Argument2.Detach();
        }

        public override bool CanBeConstant
        {
            get
            {
                return base.CanBeConstant && Argument1.CanBeConstant && Argument2.CanBeConstant;
            }
        }

        public override bool IsParameterFree
        {
            get { return Target.IsParameterFree && Argument1.IsParameterFree && Argument2.IsParameterFree; }
        }

        public override IObservableExpression<TResult> ApplyParameters(IDictionary<string, object> parameters)
        {
            return new ObservableMethodCall<T, T1, T2, TResult>(Target.ApplyParameters(parameters), Method, Argument1.ApplyParameters(parameters), Argument2.ApplyParameters(parameters));
        }
    }

    internal class ObservableMethodCall<T, T1, T2, T3, TResult> : ObservableMethodBase<T, Func<T1, T2, T3, TResult>, TResult>
    {
        public ObservableMethodCall(MethodCallExpression node, ObservableExpressionBinder binder)
            : this(binder.VisitObservable<T>(node.Object), node.Method,
                binder.VisitObservable<T1>(node.Arguments[0]),
                binder.VisitObservable<T2>(node.Arguments[1]),
                binder.VisitObservable<T3>(node.Arguments[2])) { }

        public ObservableMethodCall(IObservableExpression<T> target, MethodInfo method, IObservableExpression<T1> arg1, IObservableExpression<T2> arg2, IObservableExpression<T3> arg3)
            : base(target, method)
        {
            if (arg1 == null) throw new ArgumentNullException("arg1");
            if (arg2 == null) throw new ArgumentNullException("arg2");
            if (arg3 == null) throw new ArgumentNullException("arg3");

            Argument1 = arg1;
            Argument2 = arg2;
            Argument3 = arg3;
        }

        public IObservableExpression<T1> Argument1 { get; private set; }

        public IObservableExpression<T2> Argument2 { get; private set; }

        public IObservableExpression<T3> Argument3 { get; private set; }

        protected override TResult GetValue()
        {
            return Function(Argument1.Value, Argument2.Value, Argument3.Value);
        }

        protected override void AttachCore()
        {
            base.AttachCore();
            var argChanged = new EventHandler<ValueChangedEventArgs>(ArgumentChanged);
            Argument1.Attach();
            Argument1.ValueChanged += argChanged;
            Argument2.Attach();
            Argument2.ValueChanged += argChanged;
            Argument3.Attach();
            Argument3.ValueChanged += argChanged;
        }

        protected override void DetachCore()
        {
            base.DetachCore();
            var argChanged = new EventHandler<ValueChangedEventArgs>(ArgumentChanged);
            Argument1.ValueChanged -= argChanged;
            Argument1.Detach();
            Argument2.ValueChanged -= argChanged;
            Argument2.Detach();
            Argument3.ValueChanged -= argChanged;
            Argument3.Detach();
        }

        public override bool CanBeConstant
        {
            get
            {
                return base.CanBeConstant && Argument1.CanBeConstant && Argument2.CanBeConstant && Argument3.CanBeConstant;
            }
        }

        public override bool IsParameterFree
        {
            get
            {
                return Target.IsParameterFree
                    && Argument1.IsParameterFree
                    && Argument2.IsParameterFree
                    && Argument3.IsParameterFree;
            }
        }

        public override IObservableExpression<TResult> ApplyParameters(IDictionary<string, object> parameters)
        {
            return new ObservableMethodCall<T, T1, T2, T3, TResult>(Target.ApplyParameters(parameters), Method,
                Argument1.ApplyParameters(parameters),
                Argument2.ApplyParameters(parameters),
                Argument3.ApplyParameters(parameters));
        }
    }

    internal class ObservableMethodCall<T, T1, T2, T3, T4, TResult> : ObservableMethodBase<T, Func<T1, T2, T3, T4, TResult>, TResult>
    {
        public ObservableMethodCall(MethodCallExpression node, ObservableExpressionBinder binder)
            : this(binder.VisitObservable<T>(node.Object), node.Method,
                binder.VisitObservable<T1>(node.Arguments[0]),
                binder.VisitObservable<T2>(node.Arguments[1]),
                binder.VisitObservable<T3>(node.Arguments[2]),
                binder.VisitObservable<T4>(node.Arguments[3])) { }

        public ObservableMethodCall(IObservableExpression<T> target, MethodInfo method, 
            IObservableExpression<T1> arg1, 
            IObservableExpression<T2> arg2,
            IObservableExpression<T3> arg3,
            IObservableExpression<T4> arg4)
            : base(target, method)
        {
            if (arg1 == null) throw new ArgumentNullException("arg1");
            if (arg2 == null) throw new ArgumentNullException("arg2");
            if (arg3 == null) throw new ArgumentNullException("arg3");
            if (arg4 == null) throw new ArgumentNullException("arg4");

            Argument1 = arg1;
            Argument2 = arg2;
            Argument3 = arg3;
            Argument4 = arg4;
        }

        public IObservableExpression<T1> Argument1 { get; private set; }

        public IObservableExpression<T2> Argument2 { get; private set; }

        public IObservableExpression<T3> Argument3 { get; private set; }

        public IObservableExpression<T4> Argument4 { get; private set; }

        protected override TResult GetValue()
        {
            return Function(Argument1.Value, Argument2.Value, Argument3.Value, Argument4.Value);
        }

        protected override void AttachCore()
        {
            base.AttachCore();
            var argChanged = new EventHandler<ValueChangedEventArgs>(ArgumentChanged);
            Argument1.Attach();
            Argument1.ValueChanged += argChanged;
            Argument2.Attach();
            Argument2.ValueChanged += argChanged;
            Argument3.Attach();
            Argument3.ValueChanged += argChanged;
            Argument4.Attach();
            Argument4.ValueChanged += argChanged;
        }

        protected override void DetachCore()
        {
            base.DetachCore();
            var argChanged = new EventHandler<ValueChangedEventArgs>(ArgumentChanged);
            Argument1.ValueChanged -= argChanged;
            Argument1.Detach();
            Argument2.ValueChanged -= argChanged;
            Argument2.Detach();
            Argument3.ValueChanged -= argChanged;
            Argument3.Detach();
            Argument4.ValueChanged -= argChanged;
            Argument4.Detach();
        }

        public override bool CanBeConstant
        {
            get
            {
                return base.CanBeConstant && Argument1.CanBeConstant && Argument2.CanBeConstant && Argument3.CanBeConstant && Argument4.CanBeConstant;
            }
        }

        public override bool IsParameterFree
        {
            get
            {
                return Target.IsParameterFree
                    && Argument1.IsParameterFree
                    && Argument2.IsParameterFree
                    && Argument3.IsParameterFree
                    && Argument4.IsParameterFree;
            }
        }

        public override IObservableExpression<TResult> ApplyParameters(IDictionary<string, object> parameters)
        {
            return new ObservableMethodCall<T, T1, T2, T3, T4, TResult>(Target.ApplyParameters(parameters), Method,
                Argument1.ApplyParameters(parameters),
                Argument2.ApplyParameters(parameters),
                Argument3.ApplyParameters(parameters),
                Argument4.ApplyParameters(parameters));
        }
    }

}
